/**********************************************************************
Compile : gcc -O3 -o enumerate enumerate.c check.c
**********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

extern int network_inferred (int N, int L, int pathway[L][N], int mode1, int mode2);

void print_help ()
{
	printf ("Usage         : ./enumerate mode1 mode2 flag N L NL NU\n");
	printf ("                (./enumerate 0 0 0 6 7 0 36)\n");
	printf ("mode1         : 0 disallow self-degradation and self-activation; 1 allow self-degradation; 2 allow self-degradation and self-activation\n");
	printf ("mode2         : 0 no Restrictions ; 1 Restriction I ; 2 Restriction II; 3 Restriction I and II\n");
	printf ("flag          : 1 filter the processes with the duplicated column\n");
	printf ("N             : number of the nodes\n");
	printf ("L             : number of the steps\n");
	printf ("NL            : minimum activity\n");
	printf ("NU            : maximum activity\n");
}

int bricknum;
long long int solunum1, solunum2;

int check_bricks (int L, int brick[L], int mode)
{
	int i;

	if (mode != 2)
	{
		if (brick[0] == 0 && brick[1] == 1 && brick[2] == 0)
			return 0;
		if (brick[0] == 1 && brick[1] == 0 && brick[2] == 1)
			return 0;
	}

	for (i = 1; i < L - 3; i++)
	{
		if (brick[i] == 0 && brick[i + 1] == 1 && brick[i + 2] == 0)
			return 0;
		if (brick[i] == 1 && brick[i + 1] == 0 && brick[i + 2] == 1)
			return 0;
	}

	if (mode == 1)
	{
		if (brick[L - 3] == 0 && brick[L - 2] == 1 && brick[0] == 0)
			return 0;
		if (brick[L - 3] == 1 && brick[L - 2] == 0 && brick[0] == 1)
			return 0;
		if (brick[L - 2] == 0 && brick[L - 1] == 1 && brick[1] == 0)
			return 0;
		if (brick[L - 2] == 1 && brick[L - 1] == 0 && brick[1] == 1)
			return 0;
	}

	return 1;
}

void combination1 (int L, int n, int brick[L], int bricks[][L + 1], int mode1, int mode2)
{
	int i;

	if (mode1 == 2 && n == L - 2)
	{
		brick[L - 2] = brick[0];
		brick[L - 1] = brick[0];

		if (mode2 == 0 || mode2 == 2 || check_bricks (L, brick, mode1) == 1) /* Restriction I forbid time series 0->1->0 / 1->0->1 */
		{
			memcpy (bricks[bricknum] + 1, brick, sizeof (int) * L);
			bricks[bricknum][0] = 0;
			for (i = 0; i < L - 2; i++)
			{
				bricks[bricknum][0] += brick[i];
			}
			if (bricks[bricknum][0] != 0 && bricks[bricknum][0] != L - 2)
				bricknum ++;
		}
	}
	else if (n == L - 1)
	{
		if (mode1 == 0)
			brick[L - 1] = brick[L - 2];
		else if (mode1 == 1)
			brick[L - 1] = brick[0];

		if (mode2 == 0 || mode2 == 2 || check_bricks (L, brick, mode1) == 1) /* Restriction I forbid time series 0->1->0 / 1->0->1 */
		{
			memcpy (bricks[bricknum] + 1, brick, sizeof (int) * L);
			bricks[bricknum][0] = 0;
			for (i = 0; i < L - 1; i++)
			{
				bricks[bricknum][0] += brick[i];
			}
			if (bricks[bricknum][0] != 0 && bricks[bricknum][0] != L - 1)
				bricknum ++;
		}
	}
	else
	{
		brick[n] = 0;
		combination1 (L, n + 1, brick, bricks, mode1, mode2);
		brick[n] = 1;
		combination1 (L, n + 1, brick, bricks, mode1, mode2);
	}
}

int compare (int brick1[], int brick2[])
{
	int i;

	for (i = 0; ; i++)
	{
		if (brick1[i] > brick2[i])
			return -1;
		else if (brick1[i] < brick2[i])
			return 1;
	}
}

void combination2 (int N, int L, int n, int select[N], int bricks[][L + 1], int pathway[L][N], int NP, int NL, int NU, int T, int mode, int mode1, int mode2, int filter)
{
	int i, j, p, flag;

	if (n == N - 1)
	{
		if (NP > NU || NP < NL)
			return;

		if (T == 0)
			return;

		if (mode == 2)
			p = 1;
		else
			p = 0;

		for (i = p; i < N; i++)
		{
			for (j = 0; j < L; j++)
			{
				pathway[j][i] = bricks[select[i]][j + 1];
			}
		}

		/* check all-zero state for no self-activation */
		for (i = 0; i < L - 2; i++)
		{
			flag = 0;
			for (j = 0; j < N; j++)
			{
				flag += pathway[i][j];
			}
			if (flag == 0)
				return;
		}

		/* check fix point */
		for (i = 1; i < L - 2; i++)
		{
			flag = 0;
			for (j = 0; j < N; j++)
			{
				if (pathway[i][j] != pathway[i + 1][j])
				{
					flag = 1;
					break;
				}
			}
			if (flag == 0)
				return;
		}

		/*
		flag = 0;
		for (i = 1; i < N; i++)
		{
			flag += (pathway[0][i] - pathway[1][i]) * (pathway[0][i] - pathway[1][i]);
		}
		if (flag > 2)
			return;
		*/

		solunum1 ++;

		flag = network_inferred (N, L, pathway, mode1, mode2);
		if (flag == 1)
		{
			int flag2;

			flag2 = 0;
			if (filter == 1)
			{
				for (i = p; i < N - 1; i++)
				{
					if (select[i] == select[i + 1])
					{
						flag2 = 1;
						break;
					}
				}

				if (flag2 == 1)
				{
					int pathway2[L][N - 1], x, y;

					for (x = 0; x < L; x++)
					{
						for (y = 0; y < N; y++)
						{
							if (y < i)
								pathway2[x][y] = pathway[x][y];
							else if (y > i)
								pathway2[x][y- 1] = pathway[x][y];
						}
					}

					flag = network_inferred (N - 1, L, pathway2, mode1, mode2);
					if (flag == 1)
						return;
				}
			}

			printf ("c Candidate CellCycle Pathway\n");

			for (i = 0; i < L; i++)
			{
				for (j = 0; j < N; j++)
				{
					printf ("%d ", pathway[i][j]);
				}
				printf ("\n");
			}
			solunum2 ++;
		}
	}
	else
	{
		if (n == -1)
			p = 0;
		else
			p = select[n];

		for (i = p; i < bricknum; i++)
		{
			int tmp;

			select[n + 1] = i;

			/*
			if (T + (bricks[i][1] - bricks[i][2]) * (bricks[i][1] - bricks[i][2]) > 2)
				continue;
			tmp = 2 - T - (bricks[i][1] - bricks[i][2]) * (bricks[i][1] - bricks[i][2]);
			if (N - 2 - n < tmp)
				tmp = N - 2 - n;

			if (NP + bricks[i][0] * (N - 1 - n) < NL)
				continue;

			if (NP + bricks[i][0] + 2 * (N - 2 - n - tmp) + tmp > NU)
				continue;
			*/

			if (NP + bricks[i][0] * (N - 1 - n) < NL)
				continue;

			if (NP + bricks[bricknum - 1][0] * (N - 1 - n) > NU)
				continue;

			combination2 (N, L, n + 1, select, bricks, pathway, NP + bricks[i][0], NL, NU, T + (bricks[i][1] - bricks[i][2]) * (bricks[i][1] - bricks[i][2]), mode, mode1, mode2, filter);
		}
	}
}

int main (int argc, char *argv[])
{
	int mode, mode1, mode2, filter;
	int N, L;
	int NL, NU;

	if (argc == 1)
	{
		print_help ();
		exit (0);
	}

	mode = 0;
	mode1 = atoi (argv[1]);
	mode2 = atoi (argv[2]);
	filter = atoi (argv[3]);
	N = atoi (argv[4]);
	L = atoi (argv[5]);
	NL = atoi (argv[6]);
	NU = atoi (argv[7]);

	int i, j;

	bricknum = 1;
	for (i = 0; i < L - 2; i++)
	{
		bricknum *= 2;
	}

	if (mode != 2)
		bricknum *= 2;
	
	int brick[L], bricks[bricknum][L + 1];

	bricknum = 0;
	combination1 (L, 0, brick, bricks, mode, mode2);
	qsort (bricks, bricknum, (L + 1) * sizeof (int), compare);

	/*
	for (i = 0; i < bricknum; i++)
	{
		for (j = 0; j < L + 1; j++)
		{
			printf ("%d ", bricks[i][j]);
		}
		printf ("\n");
	}
	
	exit (0);
	*/

	int pathway[L][N], select[N];

	solunum1 = solunum2 = 0;

	combination2 (N, L, -1, select, bricks, pathway, 0, NL, NU, 0, 0, mode1, mode2, filter);

	printf ("Summary : %ld %ld\n", solunum2, solunum1);

	exit (0);
}
